﻿using SanlieHash.GuoMi;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using TouchSocket.Core;
using TouchSocket.Http;
using configstore.FileSystem;
using System.Net.Http;

namespace configstore.NetLIB
{
    public class MultiDataContent : TouchSocket.Http.HttpContent
    {
        private readonly int m_bufferLength;
        private readonly int m_maxSpeed;
        internal const string PRE_BOUNDARY = "++++configstore++++";
        internal const string PRE_DISPOSITION = "Content-Disposition: form-data;";
        internal const string DEFAULT_CONTENTTYPE = "Content-Type: multipart/form-data";
        internal const string STREAM_TYPE = "Content-Type: application/octet-stream";
        internal const string BIN_TRANSFER = "Content-Transfer-Encoding: binary";
        internal const string TEXT_TYPE = "Content-Type: text/plain;charset=UTF-8";
        internal const string LINE = "\r\n";
        internal IDictionary<string, text_content> TextContents { get; set; }
        internal IDictionary<string, stream_content> StreamContents { get; set; }
        internal Guid ID { get; set; }
        internal string Tag { get; set; }
        internal string Boundary { get; set; }
        internal byte[] BoundaryEnd { get; set; }
        internal long _length { get; set; }
        public delegate void ProgressHandle(string name, long now_size, long total_size);
        /// <summary>
        /// 上传进度
        /// </summary>
        public event ProgressHandle? ProgressCallback;
        public MultiDataContent(int bufferLength = 1024 * 64, int maxSpeed = int.MaxValue)
        {
            this.ID = Guid.NewGuid();
            this.Tag = new SM3().Make(this.ID.ToByteArray());
            this.Boundary = $"--{PRE_BOUNDARY}{Tag}{LINE}";
            this.BoundaryEnd = Encoding.UTF8.GetBytes($"--{PRE_BOUNDARY}{Tag}--{LINE}");
            this.m_bufferLength = bufferLength;
            this.m_maxSpeed = maxSpeed;

            _length = BoundaryEnd.Length;
            TextContents = new Dictionary<string, text_content>();
            StreamContents = new Dictionary<string, stream_content>();
        }

        public long ContentLength { get { return _length; } }
        public void AddContent(string name, string value)
        {
            var header = new StringBuilder();
            header.Append(Boundary);
            header.Append($"{PRE_DISPOSITION} name=\"{name}\"");
            header.Append(LINE);
            header.Append(TEXT_TYPE);

            TextContents[name] = new text_content
            {
                header = Encoding.UTF8.GetBytes(header.ToString()),
                value = Encoding.UTF8.GetBytes(value)
            };
            _length += TextContents[name].header.Length;
            _length += TextContents[name].value.Length;
            _length += LINE.Length * 3;

        }
        public long AddFileContent(string name, string path)
        {
            var filename = Path.GetFileName(path);
            var header = new StringBuilder();
            header.Append(Boundary);
            header.Append($"{PRE_DISPOSITION} name=\"File\"; filename=\"{filename}\"");
            header.Append(LINE);
            header.Append(STREAM_TYPE);
            header.Append(LINE);
            header.Append(BIN_TRANSFER);

            //var file = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);

            StreamContents[name] = new stream_content
            {
                header = Encoding.UTF8.GetBytes(header.ToString()),
                //value = file,
                path = path
            };

            var filesize = PathTool.ReadFileLength(path);
            _length += StreamContents[name].header.Length;
            _length += filesize;
            _length += LINE.Length * 3;

            return filesize;
        }

        protected override bool OnBuildingContent<TByteBlock>(ref TByteBlock byteBlock)
        {
            return false;
        }

        protected override void OnBuildingHeader(IHttpHeader header)
        {
            header.Add(HttpHeaders.ContentLength, ContentLength.ToString());
            header.Add(HttpHeaders.ContentType, $"multipart/form-data; boundary={PRE_BOUNDARY}{Tag}");
        }

        protected override async Task WriteContent(Func<ReadOnlyMemory<byte>, Task> writeFunc, CancellationToken token)
        {
            foreach (var ctx in TextContents)
            {
                await writeFunc.Invoke(ctx.Value.header).ConfigureAwait(false);
                await writeFunc.Invoke(Encoding.UTF8.GetBytes(LINE)).ConfigureAwait(false);
                await writeFunc.Invoke(Encoding.UTF8.GetBytes(LINE)).ConfigureAwait(false);
                await writeFunc.Invoke(ctx.Value.value).ConfigureAwait(false);
                await writeFunc.Invoke(Encoding.UTF8.GetBytes(LINE)).ConfigureAwait(false);
            }
            if (StreamContents.Count > 0)
            {
                Memory<byte> memory = new byte[this.m_bufferLength];

                foreach (var ctx in StreamContents)
                {
                    await writeFunc.Invoke(ctx.Value.header).ConfigureAwait(false);
                    await writeFunc.Invoke(Encoding.UTF8.GetBytes(LINE)).ConfigureAwait(false);
                    await writeFunc.Invoke(Encoding.UTF8.GetBytes(LINE)).ConfigureAwait(false);

                    using (var file = File.Open(ctx.Value.path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                    {
                        long total_size = file.Length;
                        long now_size = 0;
                        while (true)
                        {
                            var r = await file.ReadAsync(memory, token);
                            if (r == 0)
                            {
                                break;
                            }
                            await writeFunc.Invoke(memory.Slice(0, r)).ConfigureAwait(false);
                            now_size += r;
                            ProgressCallback?.Invoke(ctx.Value.path, now_size, total_size);
                        }
                    }
                    await writeFunc.Invoke(Encoding.UTF8.GetBytes(LINE)).ConfigureAwait(false);

                }
            }
            await writeFunc.Invoke(BoundaryEnd).ConfigureAwait(false);
        }
        internal struct text_content
        {
            internal byte[] header { get; set; }
            internal byte[] value { get; set; }
        }

        internal struct stream_content
        {
            internal byte[] header { get; set; }
            //internal Stream value { get; set; }
            internal string path { get; set; }
        }
    }
}
